package rsee.com;

import java.util.Vector;

import ezeye.device.EyeDeviceInfo;
import ezeye.util.EyeBuffer;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;

public class EyeNetMgr extends HandlerThread implements Handler.Callback {
    // ================ Instance ================
    private static EyeNetMgr mInstance = null;
    int Version =1;
    public static EyeNetMgr getInstance() {
        if (null != mInstance)
            return mInstance;

        try {
            // priority should be lower than normal
            mInstance = new EyeNetMgr("EyeNetMgr", Thread.NORM_PRIORITY - 2);
            mInstance.start();
        } catch (Exception e) {
            // TODO: handle exception
        }

        return mInstance;
    }

    private EyeNetMgr(String name, int privority) {
        super(name, privority);
    }

    // ================ HandlerThread ================
    private Handler mHandler = null;

    public Handler handler() {
        return mHandler;
    }

    @Override
	public void start() {
        super.start();

        if (null == mHandler)
            mHandler = new Handler(getLooper(), this);
    }

    // ================ MessageInterface ================
    public class MsgInType {
        public static final int SocketIterNext = 0x10;
        public static final int SocketReq = 0x11;
        public static final int SocketWrite = 0x12;
        public static final int SocketUpdate = 0x13;
        public static final int SocketClose = 0x14;
    }

    public class MsgInArg {
        public static final int SocketStateWait = 0x10;
        public static final int SocketStateRun = 0x11;
    }

    public void postSocketReq(SourceIdentNet ident, EyeDeviceInfo deviceInfo, Handler transHandler, EyeBuffer rawBuffer, int Version) {
        if (null == mHandler)
            return;
        this.Version = Version;
        EyeSocketReqParam param = new EyeSocketReqParam(ident, deviceInfo, transHandler, rawBuffer);
        Message msg = mHandler.obtainMessage(MsgInType.SocketReq, param);
        mHandler.sendMessage(msg);
    }

    public void postSocketWrite(SourceIdentNet ident, byte[] packData,
            boolean flush) {
        if (null == mHandler)
            return;

        EyeSocketWriteParam param = new EyeSocketWriteParam(ident, packData,
                flush);
        Message msg = mHandler.obtainMessage(MsgInType.SocketWrite, param);
        mHandler.sendMessage(msg);
    }

    public void postSocketUpdate(SourceIdentNet ident, int arg1, int arg2) {
        if (null == mHandler)
            return;

        Message msg = mHandler.obtainMessage(MsgInType.SocketUpdate, arg1,
                arg2, ident);
        mHandler.sendMessage(msg);
    }

    public void postSocketClose(SourceIdent ident) {
        if (null == mHandler)
            return;

        Message msg = mHandler.obtainMessage(MsgInType.SocketClose, ident);
        mHandler.sendMessage(msg);
    }

    // ================ MessageLoop ================
    public class MsgOutType {
        public static final int SocketError = 0x100;
        public static final int SocketConnected = 0x101;
    }

    public class MsgOutParam {
        public static final int ErrorUnknown = 0x100;
        public static final int ErrorConnected = 0x101;
        public static final int ErrorRead = 0x102;
        public static final int ErrorWrite = 0x103;
        public static final int ErrorConnReset = 0x104;
    }

    @Override
    public boolean handleMessage(Message msg) {
        switch (msg.what) {
        case MsgInType.SocketIterNext:
            socketIterate();
            break;
        case MsgInType.SocketReq:
            EyeSocketReqParam paramReq = (EyeSocketReqParam) msg.obj;
            socketRequest(paramReq);
            break;
        case MsgInType.SocketWrite:
            EyeSocketWriteParam paramWrite = (EyeSocketWriteParam) msg.obj;
            byte[] packData = paramWrite.getPackData();
            if(null==packData)
            	break;
            socketWrite(paramWrite.getIdent(), packData, 0, packData.length,
                    paramWrite.getFlush());
            break;
        case MsgInType.SocketUpdate:
            SourceIdent paramUpdate = (SourceIdent) msg.obj;
            socketUpdate(paramUpdate, msg.arg1, msg.arg2);
            break;
        case MsgInType.SocketClose:
            SourceIdent paramClose = (SourceIdent) msg.obj;
            socketClose(paramClose);
            break;
        default:
            break;
        }

        return true;
    }

    private void postSocketIter() {
        boolean needLoop = needSocketIterate();
        if (needLoop) {
            boolean hasMsg = mHandler.hasMessages(MsgInType.SocketIterNext);
            if (!hasMsg) {
                Message socketMsg = mHandler
                        .obtainMessage(MsgInType.SocketIterNext);
                mHandler.sendMessage(socketMsg);
            }
        }
    }

    // ================== Network Transactions =================
    private Vector<EyeSocketWrap> mSocketArray = new Vector<EyeSocketWrap>();
    private static final int SocketRevBufferSize = 4*1024;

    public class EyeSocketReqParam {
        private SourceIdent mIdent = null;
        private Handler mTransHandler = null;
        private EyeBuffer mRawBuffer = null;
        private EyeDeviceInfo mDeviceInfo = null;

        public EyeSocketReqParam(SourceIdent ident, EyeDeviceInfo deviceInfo, Handler handler, EyeBuffer rawBuffer) {
            mIdent = ident;
            mDeviceInfo = deviceInfo;
            mTransHandler = handler;
            mRawBuffer = rawBuffer;
        }

        public SourceIdent getIdent() {
            return mIdent;
        }
        
        public EyeDeviceInfo getDeviceInfo() {
            return mDeviceInfo;
        }

        public Handler getHandler() {
            return mTransHandler;
        }
        
        public EyeBuffer rawBuffer() {
            return mRawBuffer;
        }
    }

    public class EyeSocketWriteParam {
        private SourceIdent mIdent = null;
        private byte[] mPackData = null;
        private boolean mFlush = true;

        public EyeSocketWriteParam(SourceIdent ident, byte[] packData,
                boolean flush) {
            mIdent = ident;
            mPackData = packData;
            mFlush = flush;
        }

        public SourceIdent getIdent() {
            return mIdent;
        }

        public byte[] getPackData() {
            return mPackData;
        }

        public boolean getFlush() {
            return mFlush;
        }
    }

    private EyeSocketWrap searchSocket(SourceIdent ident) {
        int count = mSocketArray.size();
        for (int i = 0; i < count; i++) {
            EyeSocketWrap wrap = mSocketArray.get(i);
            boolean exist = wrap.isSameIdent(ident);
            if (exist)
                return wrap;
        }

        return null;
    }

    private void socketRequest(EyeSocketReqParam param) {
        if (null == param)
            return;
        
        // TODO SourceIdent detect
        SourceIdent ident = param.getIdent();
        EyeDeviceInfo deviceInfo = param.getDeviceInfo();
        if(null==deviceInfo)
        	return;
        Handler transHandler = param.getHandler();
        EyeBuffer rawBuffer = param.rawBuffer();
        
        String host = deviceInfo.getHost();
        int port = deviceInfo.getPort();
        if (null == host)
            return;

        EyeSocketWrap wrap = null;
        wrap = searchSocket(ident);
        if (null != wrap) {
            // TODO state detect, reconnect
            return;
        }
        try {
            EyeSocket socket = new EyeSocket(host, port);
            socket.setReceiveBufferSize(SocketRevBufferSize);
            wrap = new EyeSocketWrap(ident, transHandler, rawBuffer, socket,Version);
            mSocketArray.add(wrap);

            // connect
            Handler handler = wrap.getHandler();
            boolean suc = socket.connect();
            if (suc) {
                Message socketMsg = handler
                        .obtainMessage(MsgOutType.SocketConnected);
                handler.sendMessage(socketMsg);
            } else {
                Message socketMsg = handler.obtainMessage(
                        MsgOutType.SocketError, MsgOutParam.ErrorConnected, 0,
                        null);
                handler.sendMessage(socketMsg);
            }
        } catch (Exception e) {
            // TODO: handle exception
        }
    }

    private void socketUpdate(SourceIdent ident, int arg1, int arg2) {
        // TODO SourceIdent detect
        EyeSocketWrap wrap = null;
        wrap = searchSocket(ident);
        if (null == wrap)
            return;

        EyeSocket socket = wrap.getSocket();
        switch (arg1) {
        case MsgInArg.SocketStateWait:
            socket.setRunning(false);
            break;
        case MsgInArg.SocketStateRun:
            socket.setRunning(true);
            break;
        default:
            break;
        }

        postSocketIter();
    }

    private void socketClose(SourceIdent ident) {
        // TODO SourceIdent detect
        EyeSocketWrap wrap = null;
        wrap = searchSocket(ident);
        if (null == wrap)
            return;

        wrap.clear();
        
        // remove from array
        mSocketArray.remove(wrap);

        //mSocketLoopIndex--;
        postSocketIter();
    }

    private void socketWrite(SourceIdent ident, byte[] packData,
            int dataOffset, int dataLen, boolean flush) {
        // TODO SourceIdent detect
        EyeSocketWrap wrap = null;
        wrap = searchSocket(ident);
        if (null == wrap)
            return;

        Handler handler = wrap.getHandler();
        EyeSocket socket = wrap.getSocket();
        if (null == socket || null == handler)
            return;

        socketWriteImpl(socket, handler, packData, dataOffset, dataLen, flush);
    }

    private void socketIterate() {
        int count = mSocketArray.size();
        if (0 >= count)
            return;
         
        for (int i = 0; i < count; i++) {
            EyeSocketWrap wrap = mSocketArray.get(i);
            socketReadImpl(wrap);
        }

        postSocketIter();
    }

    private boolean needSocketIterate() {
        int count = mSocketArray.size();
        if (0 >= count)
            return false;

        for (int i = 0; i < count; i++) {
            EyeSocketWrap wrap = mSocketArray.get(i);
            EyeSocket socket = wrap.getSocket();
            if (socket.shouldReadMore())
                return true;
        }

        return false;
    }

    private void socketReadImpl(EyeSocketWrap wrap) {
        assert(null != wrap);
        EyeSocket socket = wrap.getSocket();
        
        boolean canRead = socket.shouldReadMore();
        if (!canRead)
            return;

        byte[] rawData = wrap.getSocketBuffer(SocketRevBufferSize);
        int rawDataLen = socket.read(rawData, 0, rawData.length);
        if (rawDataLen > 0) {
            wrap.writeRawData(rawData, 0, rawDataLen);
        } else if (rawDataLen < 0) {
            // error
            Handler handler = wrap.getHandler();
            int errCode = socket.getErrCode();
            int msgOutParam = MsgOutParam.ErrorRead;
            if (errCode == EyeSocket.SocketErrCode.ErrConnReset)
                msgOutParam = MsgOutParam.ErrorConnReset;

            Message socketMsg = handler.obtainMessage(MsgOutType.SocketError,
                    msgOutParam, 0, null);
            handler.sendMessage(socketMsg);
        }
    }

    private void socketWriteImpl(EyeSocket socket, Handler handler,
            byte[] packData, int dataOffset, int dataLen, boolean flush) {
        boolean suc = socket.write(packData, dataOffset, dataLen, flush);
        if (!suc) {
            Message socketMsg = handler.obtainMessage(MsgOutType.SocketError,
                    MsgOutParam.ErrorWrite, 0, null);
            handler.sendMessage(socketMsg);
        }
    }

    private long mLogTime = 0;
    public void log(String msg) {
        long time = SystemClock.elapsedRealtime();
        Log.v("EyeNetMgr "+String.valueOf(time-mLogTime), msg);
        mLogTime = time;
    }
}
